using System;
using System.Collections;
using System.Drawing;
using System.Data;
using System.Data.SqlTypes;
using System.Data.SqlServerCe;
using System.IO;
using System.Reflection;
using System.Text;
using GriffinNav.Map.VirtualEarth;

namespace GriffinNav.Map.Data
{
    internal class DataAccess
    {
        private string mConnString = string.Empty;
        private SqlCeConnection mConn = null;
        private SqlCeCommand mCommand = null;
        private Bitmap mImageNotAvailable = null;

        internal event MapErrorHandler OnError;

        internal DataAccess(string filePath)
        {
            try
            {
                // create SQL connection
                mConnString = "Data Source = " + filePath + "; Max Database Size = 2048;";
                mConn = new SqlCeConnection(mConnString);

                // create SQL command object
                mCommand = new SqlCeCommand();
                mCommand.Connection = mConn;

                // open SQL connection
                mConn.Open();

                mImageNotAvailable = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("GriffinNav.Map.Images.NotAvailable.png"));
            }
            catch (Exception ex)
            {
                throw new MapException(ex, "Error opening map file.", "DataAccess", "DataAccess()");
            }
        }

        internal void Close()
        {
            try
            {
                if (mConn.State == ConnectionState.Open)
                {
                    // close SQL connection
                    mConn.Close();
                }
            }
            catch (Exception ex)
            {
                throw new MapException(ex, "Error closing map file.", "DataAccess", "Close()");
            }
        }

        internal void Open(string filePath)
        {
            try
            {
                // check if connection to another database is open
                if (mConn.State == ConnectionState.Open)
                {
                    // change connection to use new database file
                    mConn.ChangeDatabase(filePath);
                }
                else
                {
                    // create connection string
                    mConnString = "Data Source = " + filePath + "; Max Database Size = 2048;";
                    mConn.ConnectionString = mConnString;

                    // open connection
                    mConn.Open();
                }
            }
            catch (Exception ex)
            {
                throw new MapException(ex, "Error opening map file.", "DataAccess", "Open()");
            }
        }

        internal MapInfo GetMapInfo()
        {
            SqlCeDataReader reader = null;
            string mapName = null;
            decimal longitude = 0M;
            decimal latitude = 0M;
            int[] detailLevels = null;
            int i = 0;
            ArrayList detailTemp = null;

            try
            {
                // query for map info
                mCommand.CommandText = "SELECT * FROM Map";
                reader = mCommand.ExecuteReader(CommandBehavior.SingleRow);

                //check for data to read
                if (reader.Read())
                {
                    // read data
                    mapName = reader.GetString(0); // map name
                    longitude = reader.GetDecimal(1); // center longitude
                    latitude = reader.GetDecimal(2); // center latitude
                }

                // close data reader
                reader.Close();

                // query for distinct detail levels
                mCommand.CommandText = "SELECT DetailLevel FROM MapDetailLevel ORDER BY DetailLevel ASC";
                reader = mCommand.ExecuteReader();

                // create temporary array list for detail levels
                detailTemp = new ArrayList();

                // read data
                while (reader.Read())
                {
                    detailTemp.Add(reader.GetInt16(0));
                    i++;
                }

                // close data reader
                reader.Close();

                // copy detail levels from ArrayList into integer array
                detailLevels = new int[i];
                detailTemp.CopyTo(detailLevels);

                // clear temp array list
                detailTemp.Clear();

                return new MapInfo(mapName, mConn.DataSource, latitude, longitude, detailLevels);
            }
            catch (Exception ex)
            {
                throw new MapException(ex, "Error retriving map information.", "DataAccess", "GetMapInfo()");
            }
        }

        internal void GetDetailLevelBounds(int levelOfDetail, out Rectangle tileBounds, out Rectangle pixelBounds)
        {
            SqlCeDataReader reader = null;
            tileBounds = Rectangle.Empty;
            pixelBounds = Rectangle.Empty;

            try
            {
                // get the tile & pixel boundaries for supplied detail level
                mCommand.CommandText = "SELECT MinTileX, MaxTileX, MinTileY, MaxTileY FROM MapDetailLevel WHERE DetailLevel = " + levelOfDetail + ";";
                reader = mCommand.ExecuteReader(CommandBehavior.SingleRow);

                if (reader.Read())
                {
                    // set upper left tile
                    tileBounds.X = Convert.ToInt32(reader.GetValue(0));
                    tileBounds.Y = Convert.ToInt32(reader.GetValue(2));

                    // set tile dimensions
                    tileBounds.Width = (Convert.ToInt32(reader.GetValue(1)) - Convert.ToInt32(reader.GetValue(0))) + 1;
                    tileBounds.Height = (Convert.ToInt32(reader.GetValue(3)) - Convert.ToInt32(reader.GetValue(2))) + 1;

                    // set upper left pixel
                    pixelBounds.X = tileBounds.X * 256;
                    pixelBounds.Y = tileBounds.Y * 256;

                    // set pixel dimensions
                    pixelBounds.Width = tileBounds.Width * 256;
                    pixelBounds.Height = tileBounds.Height * 256;
                }
            }
            catch (Exception ex)
            {
                if (OnError != null)
                {
                    OnError(new MapException(ex, "Error retriving map information.", "DataAccess", "GetDetailLevelBounds()"), true);
                }
            }
        }

        internal Bitmap GetTileImage(int tileX, int tileY, int levelOfDetail)
        {
            Bitmap tileImage = null;

            try
            {
                // get a single tile image from the database
                mCommand.CommandText =
                    "SELECT MapTileImage " +
                    "FROM MapTile WHERE " +
                        "MapTileX=" + tileX + " AND " +
                        "MapTileY=" + tileY + " AND " +
                        "MapTileDetailLevel=" + levelOfDetail;

                // get image bytes
                byte[] imgArr = (byte[])mCommand.ExecuteScalar();

                if (imgArr != null && imgArr.Length > 0)
                {
                    // create bitmap image from bytes
                    tileImage = new Bitmap(new MemoryStream(imgArr, 0, imgArr.Length));
                }

                return tileImage;
            }
            catch (Exception ex)
            {
                if (OnError != null)
                {
                    OnError(new MapException(ex, "Error reading map data.", "DataAccess", "GetTileImage()"), true);
                }

                return null;
            }
        }

        internal void GetTileBuffer(int centerTileX, int centerTileY, int levelOfDetail, ref Graphics bufferGraphics, int bufferTileSize, int tilePixelSize, out Rectangle tileBounds, out Rectangle pixelBounds)
        {
            SolidBrush backgroundBrush = new SolidBrush(Color.LightGray);
            Bitmap tileImage = null;
            int count = 0;

            try
            {
                bufferGraphics.FillRectangle(backgroundBrush, 0, 0, bufferTileSize * tilePixelSize, bufferTileSize * tilePixelSize);

                int minX = (centerTileX + ((1 - bufferTileSize) / 2));
                int maxX = (centerTileX + ((bufferTileSize - 1) / 2));
                int minY = (centerTileY + ((1 - bufferTileSize) / 2));
                int maxY = (centerTileY + ((bufferTileSize - 1) / 2));

                for (int tileX = minX; tileX <= maxX; tileX++)
                {
                    for (int tileY = minY; tileY <= maxY; tileY++)
                    {
                        // get tile image
                        tileImage = GetTileImage(tileX, tileY, levelOfDetail);

                        if (tileImage != null)
                        {
                            // draw the image at its position in the tile buffer
                            bufferGraphics.DrawImage(tileImage, ((tileX + 1) - centerTileX) * tilePixelSize, ((tileY + 1) - centerTileY) * tilePixelSize);

                            count++; // increment image count
                        }
                        else
                        {
                            // draw the image at its position in the tile buffer
                            bufferGraphics.DrawImage(mImageNotAvailable, ((tileX + 1) - centerTileX) * tilePixelSize, ((tileY + 1) - centerTileY) * tilePixelSize);
                        }
                    }
                }

                if (count > 0)
                {
                    if (tileImage != null)
                    {
                        // dispose of the last tile image
                        tileImage.Dispose();
                    }

                    // set the buffer tile boundaries
                    tileBounds = new Rectangle(
                                        (centerTileX + ((1 - bufferTileSize) / 2)),
                                        (centerTileY + ((1 - bufferTileSize) / 2)),
                                        bufferTileSize,
                                        bufferTileSize);

                    // set the buffer pixel boundaries
                    pixelBounds = new Rectangle(
                                        (centerTileX + ((1 - bufferTileSize) / 2)) * tilePixelSize,
                                        (centerTileY + ((1 - bufferTileSize) / 2)) * tilePixelSize,
                                        tilePixelSize * bufferTileSize,
                                        tilePixelSize * bufferTileSize);
                }
                else
                {
                    // return empty boundaries
                    tileBounds = Rectangle.Empty;
                    pixelBounds = Rectangle.Empty;
                }
            }
            catch (Exception ex)
            {
                // return empty boundaries
                tileBounds = Rectangle.Empty;
                pixelBounds = Rectangle.Empty;

                if (OnError != null)
                {
                    OnError(new MapException(ex, "Error reading map data.", "DataAccess", "GetTileBuffer()"), true);
                }
            }
        }
    }
}
